/* Turska Framework/UI library (Turs2)
 * Copyright 2003-2010 Jetro Lauha
 * All rights reserved.
 * Web: http://iki.fi/jetro/turska/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of EITHER:
 *   (1) The GNU Lesser General Public License as published by the Free
 *       Software Foundation; either version 2.1 of the License, or (at
 *       your option) any later version. The text of the GNU Lesser
 *       General Public License is included with this library in the
 *       file LICENSE-LGPL.txt.
 *   (2) The BSD-style license that is included with this library in
 *       the file LICENSE-BSD.txt.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
 * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
 *
 * $Id: T2Font.cpp 28 2012-07-28 17:03:06Z trzntaat $
 * $Revision: 28 $
 */

#include "Turs2.h"


namespace turska
{


Font::StringRowParser::StringRowParser(Font *font, const C8 *string, TREAL scale, TREAL wrapAreaWidth, UI32 substrLength)
{
    reset(font, string, scale, wrapAreaWidth, substrLength);
}


bool Font::StringRowParser::reset(Font *font, const C8 *string, TREAL scale, TREAL wrapAreaWidth, UI32 substrLength)
{
    if (string == 0 || string[0] == 0 || font == 0)
    {
        mFont = 0;
        mString = 0;
        mMaxLength = 0;
        mResultRowCount = 0;
        mDone = true;
        return false;
    }
    const int SPACE = 32;
    mFont = font;
    mString = string;
    mScale = scale;
    mWrapAreaWidth = wrapAreaWidth;
    mMaxLength = substrLength ? substrLength : 0x7fffffff;
    mWordWrapping = (wrapAreaWidth > 0);
    mSpacing = font->getCharacterSpacing() * scale;
    mSpaceWidth = (font->hasCharacter(SPACE) ? font->getStringWidth(" ", 1) : font->getFontHeight() / 3) * scale;
    mResultWidth = 0;
    mResultRowCount = 1;
    mTotalChars = 0;
    mCurrentRowStart = string;
    mCurrentWordStart = string;
    mCurrentRowLength = 0;
    mCurrentWordLength = 0;
    //mCurrentRowWordCount = 0;
    mCurrentRowWidth = 0;
    mDone = false;
    return true;
}


bool Font::StringRowParser::getNextRow(const C8 *&oRowStart, UI32 &oRowLength, TREAL *oRowWidth)
{
    if (finished())
    {
        oRowStart = &mString[mTotalChars];
        oRowLength = 0;
        if (oRowWidth)
            *oRowWidth = 0;
        return false;
    }

    const int SPACE = 32, NEWLINE = '\n', TERMINATOR = 0;
    bool rowDone = false, escape = false;

    I32 prevTotalChars;
    C8 currentChar;
    do {
        currentChar = mString[mTotalChars];
        if (mFont->getFlags() & Font::FF_HANDLE_BACKSLASH_ESCAPES)
        {
            if (escape)
            {
                switch (currentChar)    // todo: wider support for different escapes
                {
                case 'n': currentChar = '\n'; break;
                case 'r': currentChar = '\r'; break;
                case 't': currentChar = '\t'; break;
                }
                escape = false;
            }
            else if (currentChar == '\\')
            {
                escape = true;
                ++mTotalChars;
                continue;
            }
        }

        bool newLineAlreadyHandled = false;
        if (mWordWrapping)
        {
            if (currentChar != SPACE && currentChar != NEWLINE && currentChar != TERMINATOR && mTotalChars < mMaxLength)
                ++mCurrentWordLength;
            else
            {
                // reached newline or space (a word has just ended if prev. char wasn't space)
                bool wrapToNextLine = false, startNewRow = false;
                TREAL newWordWidth = 0;
                if (mCurrentWordLength > 0)
                {
                    // end of new word reached - check if row would extend beyond wrap area
                    newWordWidth = mFont->getStringWidth(mCurrentWordStart, mScale, mCurrentWordLength);
                    if (mCurrentRowWidth + newWordWidth > mWrapAreaWidth)
                    {
                        if (mCurrentRowWidth > 0)
                        {
                            // word goes beyond wrap area width
                            wrapToNextLine = true;
                            newWordWidth += mSpacing;
                        } else {
                            // let word overflow from wrapped area since it doesn't fit
                            startNewRow = true;
                        }
                    }
                    else
                        mCurrentRowWidth += newWordWidth + mSpacing;
                }
                if (!wrapToNextLine && mCurrentRowWidth + mSpaceWidth >= mWrapAreaWidth && currentChar != TERMINATOR && mTotalChars < mMaxLength)
                    startNewRow = true;
                if (wrapToNextLine || startNewRow)
                {
                    // start new line (wrap current word to beginning of new line)
                    if (oRowWidth)
                        *oRowWidth = mCurrentRowWidth;
                    oRowStart = mCurrentRowStart;
                    // when wrapping, subtract current word length from result row, or when
                    // starting new row proactively (next space wouldn't fit), include current char
                    oRowLength = mCurrentRowLength + (wrapToNextLine ? -mCurrentWordLength : 1);
                    ++mResultRowCount;
                    mCurrentRowStart = wrapToNextLine ? mCurrentWordStart : &mString[mTotalChars + 1];
                    mCurrentRowWidth = wrapToNextLine ? newWordWidth + 1 : 0;
                    mCurrentRowLength = wrapToNextLine ? mCurrentWordLength + 1 : 0;
                    //mCurrentRowWordCount = 0;
                    newLineAlreadyHandled = true; // signal that newline has been handled by word wrapping
                    rowDone = true;
                }
                else if (currentChar == SPACE)
                {
                    // space is added to current line
                    mCurrentRowWidth += mSpaceWidth + mSpacing;
                }
                // start next word from next char (current is space or newline)
                if (currentChar != TERMINATOR)
                    mCurrentWordStart = &mString[mTotalChars + 1];
                //if (!wrapToNextLine)
                mCurrentWordLength = 0;
                //++mCurrentRowWordCount;
            } // current char == SPACE or NEWLINE
        } // wordWrapping

        if (!rowDone && currentChar == NEWLINE)
        {
            if (!newLineAlreadyHandled)
            {
                if (!mWordWrapping && oRowWidth)
                    *oRowWidth = mFont->getStringWidth(mCurrentRowStart, mScale, mCurrentRowLength);
                oRowStart = mCurrentRowStart;
                oRowLength = mCurrentRowLength;
                ++mResultRowCount;
                mCurrentRowStart = &mString[mTotalChars + 1];
                mCurrentRowWidth = 0;
                mCurrentRowLength = 0;
                //mCurrentRowWordCount = 0;
                newLineAlreadyHandled = true;
                rowDone = true;
            }
        } // current char == NEWLINE

        if (!newLineAlreadyHandled)
            ++mCurrentRowLength;

        prevTotalChars = mTotalChars;
        if (mTotalChars < mMaxLength && currentChar != TERMINATOR)
            ++mTotalChars;
    } while (!rowDone && prevTotalChars < mMaxLength && currentChar != TERMINATOR);

    if (!mDone && !rowDone && (mTotalChars >= mMaxLength || mString[mTotalChars] == TERMINATOR))
    {
        mDone = true;
        if (mCurrentRowLength > 0)
        {
            if (oRowWidth)
                *oRowWidth = mFont->getStringWidth(mCurrentRowStart, mScale, mCurrentRowLength);
            oRowStart = mCurrentRowStart;
            if (mTotalChars >= mMaxLength)
                --mCurrentRowLength; // subtract last char (already went past asked length)
            oRowLength = mCurrentRowLength;
            rowDone = true;
        }
    }

    return rowDone;
}


I32 Font::getStringMetrics(TREAL &oWidth, TREAL &oHeight, const C8 *string,
                           TREAL scale, TREAL wrapAreaWidth, UI32 substrLength)
{
    StringRowParser srp(this, string, scale, wrapAreaWidth, substrLength);
    if (srp.finished())
        return 0;

    TREAL resultWidth = 0;
    const C8 *rowStart;
    UI32 rowLength;
    TREAL rowWidth;
    while (srp.getNextRow(rowStart, rowLength, &rowWidth))
    {
        if (resultWidth < rowWidth)
            resultWidth = rowWidth;
    }
    oWidth = resultWidth;
    oHeight = getFontHeight(scale) * srp.getResultRowCount();
    return srp.getResultRowCount();
}


I32 Font::drawText(TREAL x, TREAL y, const C8 *text,
                   UI32 flags, TREAL scale, TREAL wrapAreaWidth,
                   const Color &color, UI32 substrLength)
{
    // align vertically first when needed
    if ((flags & IF_ALIGN_VERT_CENTER) || (flags & IF_ALIGN_BOTTOM))
    {
        TREAL totalWidth = 0, totalHeight = 0;
        getStringMetrics(totalWidth, totalHeight, text, scale, wrapAreaWidth, substrLength);
        if ((flags & IF_ALIGN_VERT_CENTER) != 0)
            y -= totalHeight / 2;
        else if ((flags & IF_ALIGN_BOTTOM) != 0)
            y -= totalHeight;
        flags &= ~(IF_ALIGN_VERT_CENTER | IF_ALIGN_BOTTOM); // mask away vertical align flags
    }

    StringRowParser srp(this, text, scale, wrapAreaWidth, substrLength);
    if (srp.finished())
        return 0;

    const C8 *rowStart;
    UI32 rowLength;
    TREAL rowWidth, rowHeight = getFontHeight(scale);
    while (srp.getNextRow(rowStart, rowLength, &rowWidth))
    {
        drawString(x, y, rowStart, flags, scale, color, rowLength);
        y += rowHeight;
    }
    return srp.getResultRowCount();
}


} // namespace turska
